package ru.bragnikita.test.https;

/**
 * @author Nikita.Brazhnikov
 */

import com.sun.net.httpserver.*;
import org.apache.commons.io.IOUtils;

import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.TrustManagerFactory;
import javax.xml.ws.BindingProvider;
import javax.xml.ws.http.HTTPException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.InetSocketAddress;
import java.security.KeyStore;
import java.util.*;

public class RabbitCounterPublisher {
	private Map<Integer, Integer> fibs;

	public RabbitCounterPublisher() {
		fibs = Collections.synchronizedMap(new HashMap<Integer, Integer>());
	}

	public static void main(String[] args) {
		new RabbitCounterPublisher().publish();
	}

	public Map<Integer, Integer> getMap() {
		return fibs;
	}

	private void publish() {
		int port = 9876;
		String ip = "https://localhost:";
		String path = "/fib";
		String url_string = ip + port + path;
		HttpsServer server = get_https_server(ip, port, path);
		HttpContext http_ctx = server.createContext(path);
		System.out.println("Publishing RabbitCounter at " + url_string);
		if (server != null) server.start();
		else System.err.println("Failed to start server. Exiting.");
	}

	private HttpsServer get_https_server(String ip, int port, String path) {
		HttpsServer server = null;
		try {
			InetSocketAddress inet = new InetSocketAddress(port);
// 2nd arg = max number of client requests to queue
			server = HttpsServer.create(inet, 5);
			SSLContext ssl_ctx = SSLContext.getInstance("TLS");
// password for keystore
			char[] password = "nikita".toCharArray();
			KeyStore ks = KeyStore.getInstance("JKS");
			InputStream fis = this.getClass().getClassLoader().getResourceAsStream("rc.keystore");
			ks.load(fis, password);
			KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
			kmf.init(ks, password);
			TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
			tmf.init(ks);
			ssl_ctx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
// Create SSL engine and configure HTTPS to use it.
			final SSLEngine eng = ssl_ctx.createSSLEngine();
			server.setHttpsConfigurator(new HttpsConfigurator(ssl_ctx) {
				public void configure(HttpsParameters parms) {
					parms.setCipherSuites(eng.getEnabledCipherSuites());
					parms.setProtocols(eng.getEnabledProtocols());
				}
			});
			server.setExecutor(null); // use default
			HttpContext http_context =
				 server.createContext(path, new MyHttpHandler(this));
		} catch (Exception e) {
			System.err.println(e);
		}
		return server;
	}
}

// The handle method is called on a particular request context,
// in this case on any request to the server that ends with /fib.
class MyHttpHandler implements HttpHandler {
	private RabbitCounterPublisher pub;

	public MyHttpHandler(RabbitCounterPublisher pub) {
		this.pub = pub;
	}

	public void handle(HttpExchange ex) {
		StringBuilder sb = new StringBuilder();
		sb.append(ex.getRequestMethod()).append("; ");
		sb.append(ex.getRequestURI()).append("; ");
		System.out.println(sb.toString());

		authenticate(ex);

		String verb = ex.getRequestMethod().toUpperCase();
		if (verb.equals("GET")) doGet(ex);
		else if (verb.equals("POST")) doPost(ex);
		else if (verb.equals("DELETE")) doDelete(ex);
		else throw new HTTPException(405);
	}

	private void authenticate(HttpExchange ex) {
		String username = ex.getRequestHeaders().get(BindingProvider.USERNAME_PROPERTY).get(0);
		String pass = ex.getRequestHeaders().get(BindingProvider.PASSWORD_PROPERTY).get(0);
		if (!username.equals("nikita") || !pass.equals("pass")) {
			throw new HTTPException(HttpURLConnection.HTTP_UNAUTHORIZED);
		}
	}

	private void respond_to_client(HttpExchange ex, String res) {
		try {
			ex.sendResponseHeaders(200, 0); // 0 means: arbitrarily many bytes
			OutputStream out = ex.getResponseBody();
			out.write(res.getBytes());
			out.flush();
			ex.close(); // closes all streams
		} catch (IOException e) {
			System.err.println(e);
		}
	}

	private void doGet(HttpExchange ex) {
		Map<Integer, Integer> fibs = pub.getMap();
		Collection<Integer> list = fibs.values();
		respond_to_client(ex, list.toString());
	}

	private void doPost(HttpExchange ex) {
		Map<Integer, Integer> fibs = pub.getMap();
		fibs.clear(); // clear to create a new map
		try {
			InputStream in = ex.getRequestBody();
			byte[] raw_bytes = IOUtils.toByteArray(in);
			String nums = new String(raw_bytes, "UTF-8");
			nums = nums.replace('[', '\0');
			nums = nums.replace(']', '\0');
			String[] parts = nums.split(",");
			List<Integer> list = new ArrayList<Integer>();
			for (String next : parts) {
				int n = Integer.parseInt(next.trim());
				fibs.put(n, countRabbits(n));
				list.add(fibs.get(n));
			}
			Collection<Integer> coll = fibs.values();
			String res = "POSTed: " + coll.toString();
			respond_to_client(ex, res);
		} catch (IOException e) {
		}
	}

	private void doDelete(HttpExchange ex) {
		Map<Integer, Integer> fibs = pub.getMap();
		fibs.clear();
		respond_to_client(ex, "All entries deleted.");
	}

	private int countRabbits(int n) {
		n = Math.abs(n);
		if (n < 2) return n; // easy cases
		Map<Integer, Integer> fibs = pub.getMap();
// Return cached values if present.
		if (fibs.containsKey(n)) return fibs.get(n);
		if (fibs.containsKey(n - 1) &&
			 fibs.containsKey(n - 2)) {
			fibs.put(n, fibs.get(n - 1) + fibs.get(n - 2));
			return fibs.get(n);
		}
// Otherwise, compute from scratch, cache, and return.
		int fib = 1, prev = 0;
		for (int i = 2; i <= n; i++) {
			int temp = fib;
			fib += prev;
			prev = temp;
		}
		fibs.put(n, fib);
		return fib;
	}
}